/*
 * MIT License
 *
 * Copyright (c) 2020 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.openapi.request;

import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.reflect.FieldAccess;
import cn.seaboot.commons.reflect.ObjectField;
import cn.seaboot.commons.reflect.ObjectType;
import cn.seaboot.openapi.base.MethodInfo;
import cn.seaboot.postman.request.Query;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Schema;

import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 扫描一个方法上，所包含的所有参数
 * <p>
 * {@link Query}
 * One request contain request-parameter, header, url, etc.
 * This interface use for finding all request-parameter from code.
 *
 * @author Mr.css
 * @version 2021-01-31 14:29
 */
public class DefaultQueryAnalyzer implements QueryAnalyzer {

    /**
     * 参数校验扫描
     */
    private ValidatorAnalyzer validatorAnalyzer = new DefaultValidatorAnalyzer();

    /**
     * 扫描一个方法上的所有参数信息
     * <p>
     * It will search all Query form method-parameter, value-object and annotation;
     * If get multiple Query with the same name, will merge all of them, and use first one.
     *
     * @param method method
     * @return all request-parameter of method
     */
    @Override
    @org.jetbrains.annotations.NotNull
    public List<Query> analyze(MethodInfo method) {
        Map<String, Query> ret = new HashMap<>();

        // 一个方法上的所有参数
        Map<String, Parameter> params = method.getParameters();
        for (Map.Entry<String, Parameter> entry : params.entrySet()) {
            // 获取参数类型
            Parameter parameter = entry.getValue();
            Class<?> parameterType = parameter.getType();
            ObjectType objectType = ObjectType.of(parameterType);

            // 根据参数类型，做不同解析
            if (ObjectType.isObject(objectType)) {
                // this case: 如果参数是一个对象
                analyzeValueObject(parameter, parameterType, ret);
            } else if (ObjectType.isField(objectType)) {
                // this case: 如果参数是一个字段
                Query query = new Query();
                query.setKey(entry.getKey());

                validatorAnalyzer.resolveParameter(parameter, query);

                // 合并参数配置
                mergeQuery(ret, query);
            }
        }
        // 获取注解中的参数信息
        analyzeAnnotations(method, ret);

        return new ArrayList<>(ret.values());
    }

    /**
     * 扫描一个参数对象中的字段注解
     * <p>
     * In this case: Vo is a parameter of Method, get all Query form Vo.
     *
     * @param parameter method-parameter
     * @param clazz     voType
     * @param result    queryList
     */
    private void analyzeValueObject(Parameter parameter, Class<?> clazz, Map<String, Query> result) {
        FieldAccess access = FieldAccess.create(clazz);
        ObjectField[] objectFields = access.getFields();
        for (ObjectField field : objectFields) {
            // 如果需要隐藏，则跳过
            Schema schema = field.getAnnotation(Schema.class);
            if (schema != null && schema.hidden()) {
                continue;
            }

            // 只扫描字段部分的内容
            if (ObjectType.isField(field.getObjectType())) {
                Query query = new Query();

                // 默认值 & 字段描述
                query.setKey(field.getName());
                if (schema != null) {
                    query.setValue(schema.defaultValue());
                    query.setDescription(schema.description());
                }

                validatorAnalyzer.resolveField(parameter, field.getField(), query);

                mergeQuery(result, query);
            }
        }
    }

    /**
     * 扫描注解中的参数配置
     * <p>
     * In this case: Method is marked by @Operation, get all Query form @Operation
     *
     * @param method Controller-Method
     * @param result queryList
     */
    private void analyzeAnnotations(MethodInfo method, Map<String, Query> result) {
        io.swagger.v3.oas.annotations.Parameter[] params = null;
        //扫描 @Parameters 中的 Parameter 注解
        Parameters parameters = method.getMethod().getDeclaredAnnotation(Parameters.class);
        if (parameters != null) {
            params = parameters.value();
        }

        //扫描 @Operation 中的 Parameter 注解
        if (CommonUtils.isNotEmpty(params)) {
            Operation operation = method.getMethod().getDeclaredAnnotation(Operation.class);
            if (operation != null) {
                params = operation.parameters();
            }
        }
        if (CommonUtils.isNotEmpty(params)) {
            for (io.swagger.v3.oas.annotations.Parameter param : params) {
                if (!param.hidden()) {
                    Query query = new Query();
                    query.setKey(param.name());
                    query.setValue(param.example());
                    query.setDescription(param.description());
                    mergeQuery(result, query);
                }
            }
        }
    }

    /**
     * 合并参数
     * <p>
     * 一个参数可以有很多配置，如果出现重复的参数配置，需要进行合并
     * <p>
     * If get multiple Query with the same name, will merge all of them
     *
     * @param result last return value
     * @param query  request-parameter
     */
    private void mergeQuery(Map<String, Query> result, Query query) {
        Query first = result.get(query.getKey());
        // 只取第一个，避免覆盖掉重要内容
        if (first != null) {
            if (CommonUtils.isNotEmpty(first.getValue())) {
                first.setValue(query.getValue());
            }
            if (CommonUtils.isNotEmpty(first.getDescription())) {
                first.setDescription(query.getDescription());
            }
        } else {
            result.put(query.getKey(), query);
        }
    }

    // getter/setter ---------------------------------------------------------------------------------

    public ValidatorAnalyzer getValidatorAnalyzer() {
        return validatorAnalyzer;
    }

    public void setValidatorAnalyzer(ValidatorAnalyzer validatorAnalyzer) {
        this.validatorAnalyzer = validatorAnalyzer;
    }
}
